package com.edev.support.ddd.join;

import com.edev.support.dao.BasicDao;
import com.edev.support.ddd.Repository;
import com.edev.support.ddd.utils.EntityBuilder;
import com.edev.support.ddd.utils.EntityUtils;
import com.edev.support.dsl.Join;
import com.edev.support.entity.Entity;
import com.edev.support.utils.NameUtils;
import lombok.NonNull;
import org.springframework.transaction.annotation.Transactional;

import java.io.Serializable;
import java.util.*;

/**
 * 多对多关联关系实现类（中间表模式）
 * 
 * 核心功能：
 * 1. 通过中间表维护实体间的多对多关系
 * 2. 自动管理中间表记录的增删改查
 * 3. 实现关联数据的延迟加载与批量装配
 * 
 * 实现原理：
 * - 使用中间表存储两个实体的关联关系
 * - 通过Join配置定义关联键和关联类键
 * - 事务保证关联操作原子性
 */
public class ManyToManyForJoin<E extends Entity<S>, S extends Serializable> extends AbstractRelation<E,S> {
    public ManyToManyForJoin(@NonNull Join join, @NonNull BasicDao dao) {
        super(join, dao);
    }
    /**
     * 创建中间表实体模板
     * @return 中间表空实体（用于构建查询条件）
     */
    private Entity<S> createJoinObject() {
        return (new EntityBuilder<Entity<S>, S>(join.getJoinClass())).createEntity();
    }

    /**
     * 生成中间表记录集合
     * <p>
     * 生成规则：
     * 1. 遍历主实体的关联集合
     * 2. 为每个关联实体创建中间表记录
     * 3. 为中间表记录设置主实体ID和关联实体ID
     * 4. 返回中间表记录集合
     * @param entity 主实体对象（携带有关联关系的实体集合）
     * @return 中间表实体集合
     */
    private Collection<Entity<S>> createJoinObjects(@NonNull E entity) {
        S id = entity.getId();
        String name = join.getName();
        @SuppressWarnings("unchecked")
        Collection<Entity<S>> collection = (Collection<Entity<S>>) entity.getValue(name);
        if(collection==null||collection.isEmpty()) return new ArrayList<>();

        Collection<Entity<S>> joinObjects = new ArrayList<>();
        collection.forEach(e -> {
            Entity<S> joinObj = createJoinObject();
            joinObj.setValue(join.getJoinKey(), id);
            joinObj.setValue(join.getJoinClassKey(), e.getId());
            joinObjects.add(joinObj);
        });
        return joinObjects;
    }

    /**
     * 插入中间表关联记录，并保证操作的原子性（事务性）
     * 执行逻辑：
     * 1. 生成中间表记录集合
     * 2. 批量插入中间表记录
     * @param entity 主实体对象（携带有关联关系的实体集合）
     */
    @Override
    @Transactional
    public void insertValue(E entity) {
        if(entity==null) return;
        Collection<Entity<S>> joinObjects = createJoinObjects(entity);
        joinObjects.forEach(dao::insert);
    }

    /**
     * 更新中间表关联记录（事务性）
     * 实现策略：
     * 1. 对比提交的和数据库中的中间表记录的差异
     * 2. 删除多余记录，插入新增记录（中间表记录不允许变更操作）
     * 3. 保持原子操作避免数据不一致
     * @param entity 主实体对象（携带有关联关系的实体集合）
     */
    @Override
    @Transactional
    public void updateValue(E entity) {
        if(entity==null) return;
        Collection<Entity<S>> collection = createJoinObjects(entity);

        String joinKey = join.getJoinKey();
        String joinClassKey = join.getJoinClassKey();
        Entity<S> template = createJoinObject();
        template.setValue(joinKey, entity.getId());
        Collection<Entity<S>> source = dao.loadAll(template);
        EntityUtils.compareListOrSetOfEntity(source, collection, (sourceEntity, targetEntity)->
                targetEntity.getValue(joinKey).equals(sourceEntity.getValue(joinKey))
                &&targetEntity.getValue(joinClassKey).equals(sourceEntity.getValue(joinClassKey)),
                (inserted, updated, deleted) -> {
                    inserted.forEach(dao::insert);
                    deleted.forEach(dao::delete);
        });
    }

    /**
     * 删除中间表关联记录（事务性）
     * 清理逻辑：
     * 1. 根据主实体ID查询中间表记录
     * 2. 批量删除所有关联的中间表记录
     * @param entity 主实体对象（携带有关联关系的实体集合）
     */
    @Override
    @Transactional
    public void deleteValue(E entity) {
        if(entity==null) return;
        S id = entity.getId();
        String joinKey = join.getJoinKey();
        Entity<S> template = createJoinObject();
        template.setValue(joinKey, id);
        Collection<Entity<S>> joinObjects = dao.loadAll(template);
        dao.deleteForList(joinObjects);
    }

    /**
     * 查找并装配单个实体的关联实体集合
     * 优化措施：
     * - 使用Repository.setNotJoin防止循环加载
     * - 批量查询提升性能
     */
    @SuppressWarnings("unchecked")
    @Override
    public void setValue(E entity) {
        if(entity==null) return;

        // 查找所有的中间表记录，获取关联实体ID列表
        S id = entity.getId();
        String joinKey = join.getJoinKey();
        Entity<S> template = createJoinObject();
        template.setValue(joinKey, id);
        Collection<Entity<S>> joinObjects = dao.loadAll(template);

        // 根据所有的中间表记录的ID，批量查找对应的关联实体集合
        List<S> ids = new ArrayList<>();
        joinObjects.forEach(e-> ids.add((S)e.getValue(join.getJoinClassKey())));
        Repository.setNotJoin(true);
        Collection<Entity<S>> collection = dao.loadForList(ids, EntityUtils.getClassOfEntity(join.getClazz()));
        Repository.setNotJoin(false);

        // 将关联实体集合装配到实体中
        entity.setValue(join.getName(), collection);
    }

    /**
     * 批量装配实体列表的关联集合
     * 优化策略：
     * 1. 使用IN语句批量查询中间表
     * 2. 分组处理减少数据库访问次数
     * 3. 保持与单个装配相同的数据一致性
     */
    @SuppressWarnings("unchecked")
    @Override
    public void setValueForList(Collection<E> list) {
        if(list==null||list.isEmpty()) return;
        String joinKey = join.getJoinKey();
        List<S> ids = new ArrayList<>();
        for(Entity<S> entity : list) ids.add(entity.getId());

        // 根据实体ID列表，批量查询所有中间表记录
        Map<Object, Object> map = new HashMap<>();
        map.put("key", NameUtils.convertToUnderline(joinKey));
        map.put("value", ids);
        map.put("opt", "IN");
        List<Map<Object, Object>> colMap = new ArrayList<>();
        colMap.add(map);
        Collection<Entity<S>> joinObjectList = dao.loadAll(colMap, EntityUtils.getClassOfEntity(join.getJoinClass()));
        if(joinObjectList==null||joinObjectList.isEmpty()) return;

        // 根据所有的中间表记录的ID进行分组，依次查找每个实体对应的关联实体集合，并完成装配
        Map<S, List<Entity<S>>> sortedEntities = sortEntitiesByJoinKey(joinObjectList);
        list.forEach(entity->{
            S id = entity.getId();
            List<Entity<S>> joinObjects = sortedEntities.get(id);
            if(joinObjects==null||joinObjects.isEmpty()) return;

            List<S> idList = new ArrayList<>();
            joinObjects.forEach(e-> idList.add((S)e.getValue(join.getJoinClassKey())));
            Repository.setNotJoin(true);
            Collection<Entity<S>> collection = dao.loadForList(idList, EntityUtils.getClassOfEntity(join.getClazz()));
            Repository.setNotJoin(false);

            // 将关联实体集合装配到实体中
            entity.setValue(join.getName(), collection);
        });
    }
}
